using System;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Linq;
using EdmxLibrary;
using MidnightPeach.Engine.Components;
using MidnightPeach.Engine.Schema.DataSchema;

namespace MidnightPeach.Engine.Schema.Extractors
{
    public class DbmlSchemaExtractor
    {
        #region Singleton

        private static DbmlSchemaExtractor _instance;

        public static DbmlSchemaExtractor Instance
        {
            get { return _instance ?? (_instance = new DbmlSchemaExtractor()); }
        }

        private DbmlSchemaExtractor()
        {
        }

        #endregion

        protected const string True = "true";
        protected const string False = "false";
        
        public List<Table> GetUnupdatableTypes(List<Table> tables)
        {
            var onlyTables = tables.Where(x => x.TableType == TableType.Table);
            var nonIsVersionTables = onlyTables.Where(x => x.Columns.Any(y => y.IsVersion) == false);

            var query = from x in nonIsVersionTables
                        where x.Columns.Any(y => y.UpdateCheck != UpdateCheck.Never)
                        select x;

            return query.ToList();
        }

        public List<Table> GetCamelNamedTypes(List<Table> tables)
        {
            var onlyTables = tables.Where(x => x.TableType == TableType.Table);

            var query = from x in onlyTables
                        where x.Columns.Any(y => y.IsCamelName)
                        select x;

            return query.ToList();
        }

        public Database Extract(string filePath)
        {
            if (filePath.ToLower().EndsWith("dbml"))
                return ExtractDbml(filePath);
            else if (filePath.ToLower().EndsWith("edmx"))
                return ExtractEdmx(filePath);

            throw new Exception("Extract");
        }

        private Database ExtractDbml(string dbmlPath)
        {
            DbmlDataSet dataSet = new DbmlDataSet();
            dataSet.ReadXml(dbmlPath);

            Database database = ExtractDatabase(dataSet);

            foreach (DbmlDataSet.TableRow tableRow in dataSet.Database[0].GetTableRows())
            {
                foreach (DbmlDataSet.TypeRow typeRow in tableRow.GetTypeRows())
                {
                    Table table = ExtractTable(tableRow, typeRow, database);

                    foreach (DbmlDataSet.ColumnRow columnRow in typeRow.GetColumnRows())
                        ExtractColumn(columnRow, table);

                    foreach (DbmlDataSet.AssociationRow associationRow in typeRow.GetAssociationRows())
                        ExtractAssociation(associationRow, table);
                }
            }

            return database;
        }

        private Database ExtractEdmx(string edmxPath)
        {
            var container = EdmxHelper.Parse(edmxPath)[0];

            Database database = new Database(container.Name);
            database.DataContext = container.Name;

            foreach (var entity in container.Entities)
            {
                Table table = new Table(database, entity.Name);

                foreach (var property in entity.GetProperties())
                {
                    Column column = new Column(table, property.Name);
                    column.Identity = property.Identity;
                    column.PrimaryKey = property.IsKey;
                    column.Nullable = property.Nullable;
                    column.ForeignKey = property.ForeignKey;

                    column.Type = property.ScalarType.Namespace + "." + property.ScalarType.Name;

                    if (column.Nullable && column.Type.IsValueType())
                        column.Type += "?";
                }
            }

            return database;
        }

        private void ExtractAssociation(DbmlDataSet.AssociationRow associationRow, Table table)
        {
            if (associationRow.IsIsForeignKeyNull() || associationRow.IsForeignKey == False)
                return;

            var tokens = associationRow.ThisKey.Split(',');

            foreach (var token in tokens)
            {
                Column column = table.Columns.Find(x => x.Name == token.Trim());
                column.ForeignKey = true;
            }
        }

        private void ExtractColumn(DbmlDataSet.ColumnRow columnRow, Table table)
        {
            var column = new Column(table, columnRow.IsMemberNull() ? columnRow.Name : columnRow.Member);
            
            column.PrimaryKey = columnRow.IsIsPrimaryKeyNull() ? false : columnRow.IsPrimaryKey == True;
            column.Nullable = column.PrimaryKey ? false : columnRow.CanBeNull == True;
            column.Identity = columnRow.DbType.Contains("IDENTITY");
            
            column.IsVersion = columnRow.IsIsVersionNull() ? false : columnRow.IsVersion == True;
            column.UpdateCheck = (UpdateCheck) Enum.Parse(typeof (UpdateCheck), columnRow.IsUpdateCheckNull() ? "Always" : columnRow.UpdateCheck);

            column.Type = columnRow.Type;
            if (column.Nullable && column.Type.IsValueType())
                column.Type += "?";
        }

        private Table ExtractTable(DbmlDataSet.TableRow tableRow, DbmlDataSet.TypeRow typeRow, Database database)
        {
            var table = new Table(database, typeRow.Name);

            return table;
        }

        private Database ExtractDatabase(DbmlDataSet dataSet)
        {
            var database = new Database(dataSet.Database[0].Name);
            database.DataContext = dataSet.Database[0].Class;

            return database;
        }
    }
}